package testGroovy

import org.springframework.beans.BeanUtils
import org.springframework.beans.FatalBeanException
import org.springframework.util.Assert
import org.springframework.util.ClassUtils
import testGroovy.model.OtherObject
import testGroovy.model.OtherObjectType
import testGroovy.model.QuoteFieldStatus

import java.beans.PropertyDescriptor
import java.lang.reflect.Method
import java.lang.reflect.Modifier

import static org.apache.commons.beanutils.BeanUtils.cloneBean
import static org.apache.commons.beanutils.BeanUtils.copyProperty

/**
 * Created by houjinxin on 2015/5/13.
 */
public class TestDeeplyCopy {
    //忽略属性的情况下复制对象
    public static void copyPropertiesIgnore(Object source, Object target, String... ignoreProperties) {
        BeanUtils.copyProperties(source, target, ignoreProperties);
    }
    //包含属性的情况下复制对象。containProperties对象为空则复制所有属性。
    public static void copyPropertiesContain(Object source, Object target) {
        copyPropertiesContain(source, target, new String[0]);
    }

    public static void copyPropertiesContain(Object source, Object target, String... containProperties) {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");
        Class<?> actualEditable = target.getClass();
        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);
        List<String> containList = null;
        if (containProperties != null && containProperties.length > 0) {
            containList = Arrays.asList(containProperties);
        }
        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (containList == null || containList.contains(targetPd.getName()))) {
                PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null &&
                            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            Object value = readMethod.invoke(source);
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            writeMethod.invoke(target, value);
                        }
                        catch (Throwable ex) {
                            throw new FatalBeanException(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }
            }
        }
    }

    /**
     * 深度拷贝对象的指定属性
     * @param source
     * @param target
     * @param propertyPathList 由源对象和目标对象path组成的List的结合，结构如下：
     * [['sourcePath1','targetPath1'],['sourcePath2','targetPath2'],...]
     * sourcePath与targetPath 分别是source和target的属性
     */
    public static void deeplyCopyProperties(source, target, propertyPathList) {

        propertyPathList.each { sourcePropertyPath, targetPropertyPath ->
            GroovyShell shell = new GroovyShell(this.class.classLoader, new Binding([source: source, target : target]))
            // 保证父级path对应的属性不为null，如果是null，就用创建一个对应class的实例赋给父级
            def targetPropertyList = targetPropertyPath.tokenize('.')
            if(targetPropertyList.size() > 1) {
                targetPropertyList[0..-2].with { paths ->
                    paths.withIndex().collect { path, idx ->
                        paths[0..idx].join '.'
                    }
                }.each { parentPath ->
                    def targetParentPath = "target.$parentPath"
                    if (!shell.evaluate(targetParentPath)) {
                        def targetParentPathList = targetParentPath.tokenize '.'
                        def ancestorPath = targetParentPathList[0..-2].join '.'
                        def ancestorInstance = shell.evaluate ancestorPath
                        def value = ancestorInstance.metaPropertyValues.find { propValue ->
                            parentPath.tokenize('.')[-1] == propValue.name
                        }.type.newInstance()
                        shell.setVariable('value', value)
                        shell.evaluate "$targetParentPath = value"
                    }
                }
            }
            // 设置值，在为目标对象的属性赋值前要判断是否为null 如果为null不需要克隆对象
            def targetPropertyValue = shell.evaluate("source.$sourcePropertyPath")?.with { self -> cloneBean(self) }
            copyProperty target, targetPropertyPath, targetPropertyValue
        }

    }

    static main(args) {
        def source = new QuoteFieldStatus(enabled: false).with { it ->
            otherObject = new OtherObject(id: 1,name: 'otherObject')
            it
        }
        println '========================浅拷贝演示======================='
        println '原始对象' + source
        def copySource = source.clone()
        copySource.with { it ->
            otherObject.with { self ->
                otherObjectType = new OtherObjectType(id:'2')
                self
            }
            it
        }

        println '修改克隆对象后的原始对象' + source

        println '========================深拷贝演示======================='
        def target = new QuoteFieldStatus()
//        deeplyCopyProperties(source, target, [['otherObject.otherObjectType', 'otherObject.otherObjectType']])
//        deeplyCopyProperties(source, target, [['filedName', 'filedName']])
        deeplyCopyProperties(source, target, [['otherObject.otherObjectType.type', 'otherObject.otherObjectType.type']])
        println '深拷贝后目标对象' + target
        source.with { self ->
            self.otherObject.with { otherObject ->
                otherObjectType.with { otherObjectType ->
                    id = 200
                    type = 'type200'
                    otherObjectType
                }
                otherObject
            }
            self
        }
        println '修改后的源对象' + source
        println '修改源对象后的目标对象' + target

    }

}
